package ws.jdbc.concurrent.driver.sqlserver;

import java.util.Hashtable;
import ws.jdbc.concurrent.driver.sqlserver.tds.TDSMessage;
import java.io.IOException;
import java.net.Socket;
import java.sql.*;
import java.util.Map;
import java.util.Properties;
import static java.sql.ResultSet.*;
import static ws.jdbc.concurrent.driver.sqlserver.Messages.*;

/**
 * Concurrent implementation of the {@link Connection}.
 *
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class CConnection implements java.sql.Connection {

  /** Socket to communicate with the database server. */
  private Socket sock;
  /** Name of this host machine. */
  private String hostName = "";
  /** User id used to validate access to the server. */
  private String userName = "";
  /** Password used to validate access to the server. */
  private String password = "";
  /** Address of the server to connect to. */
  private String serverName = "";
  /** Port to connect to on the serverName. */
  private int portNumber = 1433;
  /** (Optional) is the instance to connect to on serverName. */
  private String instanceName = "";
  /** Database to connect to. */
  private String database = "";
  /** Object used to send request to the server. */
  private TDSMessage tdsMessage;
  /** List with the created statement of this connection. */
  private Hashtable<Integer, CStatement> stmtList = new Hashtable<Integer, CStatement>();
  /** Number of created statements. */
  private int stmtCount = 0;

  /** Test purposes only. */
  private CConnection() {
  }

  CConnection(String url, Properties info) throws SQLException {
    try {
      parseUrl(url, info);
      tdsLogin();
    } catch (final Throwable t) {
      throw new SQLException(t);
    }
  }

  @Override
  protected void finalize() throws Throwable {
    super.finalize();
    close();
  }

  public synchronized void close() throws SQLException {
    if (isClosed()) {
      /* Calling close on a closed connection is a no-op. */
      return;
    }

    try {
      /* Close created statements and remove then. */
      for (Statement stmt : stmtList.values()) {
        stmt.close();
      }
      stmtList.clear();
      stmtList = null;

      /* Close server connection. */
      tdsMessage.close();
      sock.close();
      sock = null;
    } catch (final IOException ex) {
      throw new SQLException(ex);
    }
  }

  public synchronized boolean isClosed() throws SQLException {
    return sock == null;
  }

  public synchronized Statement createStatement() throws SQLException {
    if (isClosed()) {
      throw new SQLException(Messages.msg_connection_is_closed);
    }
    CStatement stmt;

    stmt = new CStatementForwardOnlyReadOnly(++stmtCount, this, tdsMessage);
    stmtList.put(stmtCount, stmt);
    return stmt;
  }

  public synchronized Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
    if (isClosed()) {
      throw new SQLException(Messages.msg_connection_is_closed);
    }
    CStatement stmt = null;

    if (resultSetType == TYPE_FORWARD_ONLY) {
      if (resultSetConcurrency == CONCUR_READ_ONLY) {
        stmt = new CStatementForwardOnlyReadOnly(++stmtCount, this, tdsMessage);
      } else if (resultSetConcurrency == CONCUR_UPDATABLE) {
        stmt = new CStatementForwardOnlyUpdatable(++stmtCount, this, tdsMessage);
      } else {
        throw new UnsupportedOperationException(msg_illegal_type_concurrency);
      }
    } else if (resultSetType == TYPE_SCROLL_SENSITIVE) {
      if (resultSetConcurrency == CONCUR_READ_ONLY) {
        stmt = new CStatementScrollableReadOnly(++stmtCount, this, tdsMessage);
      } else if (resultSetConcurrency == CONCUR_UPDATABLE) {
        stmt = new CStatementScrollableUpdatable(++stmtCount, this, tdsMessage);
      }
    } else {
      throw new UnsupportedOperationException(msg_unsupported_type_concurrency);
    }
    stmtList.put(stmtCount, stmt);
    return stmt;
  }

  /**
   * Remove statement from the list of created statements.
   *
   * @param id statement id.
   */
  synchronized void removeStatement(int id) {
    stmtList.remove(id);
  }

  private void tdsLogin() throws SQLException {
    try {
      sock = new Socket(serverName, portNumber);
      tdsMessage = new TDSMessage(sock);
      tdsMessage.login(hostName, userName, password, serverName, database);
    } catch (IOException ex) {
      throw new SQLException(ex);
    }
  }

  private void parseUrl(String url, Properties info) throws SQLException {
    getPropertiesFromUrl(url);
    url = url.substring((CDriver.subProtocol + "//").length());

    String[] urlSplit = url.split(":");
    if (urlSplit.length > 0) {
      String[] serverSplit = urlSplit[0].split("\\\\");
      serverName = serverSplit[0];
      if (serverSplit.length > 1) {
        instanceName = serverSplit[1];
      }
      if (urlSplit.length > 1) {
        String[] propertySplit = urlSplit[1].split(";");
        try {
          portNumber = Integer.parseInt(propertySplit[0]);
        } catch (final NumberFormatException ex) {
          throw new SQLException(ex);
        }
      }
    }

    if (info != null && info.size() > 0) {
      userName = info.getProperty("username");
      password = info.getProperty("password");
      database = info.getProperty("database");
    }
  }

  private void getPropertiesFromUrl(String url) {
    String[] propsSplit = url.split(";");
    for (int i = 1; i < propsSplit.length; i++) {
      String[] prop = propsSplit[i].split("=");
      if (prop[0].equalsIgnoreCase("username")) {
        userName = prop[1];
      } else if (prop[0].equalsIgnoreCase("password")) {
        password = prop[1];
      } else if (prop[0].equalsIgnoreCase("database")) {
        database = prop[1];
      }
    }
  }

  // <editor-fold defaultstate="collapsed" desc="Not implemented.">
  public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public PreparedStatement prepareStatement(String sql) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public CallableStatement prepareCall(String sql) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public String nativeSQL(String sql) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setAutoCommit(boolean autoCommit) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean getAutoCommit() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void commit() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void rollback() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public DatabaseMetaData getMetaData() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setReadOnly(boolean readOnly) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean isReadOnly() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setCatalog(String catalog) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public String getCatalog() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setTransactionIsolation(int level) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public int getTransactionIsolation() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public SQLWarning getWarnings() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void clearWarnings() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public Map<String, Class<?>> getTypeMap() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setHoldability(int holdability) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public int getHoldability() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public Savepoint setSavepoint() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public Savepoint setSavepoint(String name) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void rollback(Savepoint savepoint) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void releaseSavepoint(Savepoint savepoint) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public Clob createClob() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public Blob createBlob() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public NClob createNClob() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public SQLXML createSQLXML() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean isValid(int timeout) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setClientInfo(String name, String value) throws SQLClientInfoException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public void setClientInfo(Properties properties) throws SQLClientInfoException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public String getClientInfo(String name) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public Properties getClientInfo() throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public <T> T unwrap(Class<T> iface) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }

  public boolean isWrapperFor(Class<?> iface) throws SQLException {
    throw new UnsupportedOperationException("Not supported yet.");
  }
  // </editor-fold>
}
